<?php
namespace cubox;


use cubox\provider\template\Tag;
use cubox\provider\template\Asset;
//use cubox\provider\template\Import;
class  Template{

    public string $id  = '';
    public string $path = '';
    public bool $root = true;
    public array $data = [];
    public string $webpackSaveFolder = '';
    public string $webpackUrl = '';
    public string $uploadFileUrl = '';
    public string $baseUrl;
    protected  array $plugApp = [];


    public function __construct(string $path = '',array $data = [],string $id='',bool $root = true)
    {
        $this->path = File::cleanPath($path);
        $this->data = $data;
        $this->id  =  $id;
        $this->root =  $root;
        $this->webpackUrl = '/'.WEBPACK;
        $this->webpackSaveFolder = RUN.DS.WEBPACK;

        $this->baseUrl =  str_replace(RUN.DS,'',dirname(str_replace('/',DS,$this->path)));

    }

 

    public function getHtml(string $html = ''){
        
        if (!$html){

            if (file_exists($this->path)){
                $html  = file_get_contents($this->path);    
            }else{
                return  "<!-- {$this->path} 模板文件不存在  -->";
            }
            
        }

        if ($this->id=='') $this->id = md5($html.serialize($this->data));
        return  $this->compile($html,$this->data);
    }

    public function setPlugApp(array $plugApp){
        $this->plugApp = $plugApp;
    }

    protected  function compile($html,$data){
        
        $asset = new Asset($this->id,$html,$this->path,$this->webpackUrl);
        $html =  $asset->make();
  
        $html = $this->compileSubTemplate($html,$data);
        $html = $this->compilePlug($html,$data);
        $html = $this->compileWebPack($html,$data);
        $html = $this->compileAppLabel($html,$data);
        
        
        $html = $this->compileImport($html,$data);
        $html = $this->parseExepr($html);
        $html = $this->compileFor($html,$data);
        $html = $this->compileIf($html,$data);
        

        // 
        $html = $this->compileJson($html,$data);
        $html = $this->compileValue($html,$data);

        return $html;
    }

    /**
     * 
     * 编译子模板
     * 
     * @param string $html
     * @param array $data
     * @return string
     */
    public function compileSubTemplate($html,$data):string{
        $_all = Tag::cms($html,'template');
        
        foreach($_all as $item){
           
            $param = $item['param'];
            $param =  $this->realParamValue($param,$data);
           
            if (!isset($param['path']) || $param['path']==''){
            
                $html =  str_replace($item['input'],'<!-- 参数错误 -->',$html);
            
            }else{

                if (isset($param['app'])){
                    $basePath =  ROOT.DS.APP.DS.$param['app'].DS.'template';
    
                }else{
                    $basePath = dirname($this->path);
                }
 
                $path = File::realPath(str_replace('.',DS,$param['path']),$basePath).'.html';
                
                $templateObj = new Template($path, $data,$this->id,false);
                $html =  str_replace($item['input'],$templateObj->getHtml(),$html);
            }

        }
        
        return $html;
    }

    protected function compilePlug($html,$data):string{
        
        $_all = Tag::cms($html,'plug');

        $allApp = array_keys(Cubox::getAppList());

        foreach($_all as $item){

            $param = $item['param'];
            $content = '';
            if (isset($param['name'])){
  
                foreach($allApp as $app){
                    $path = ROOT.DS.APP.DS.$app.DS.'Template'.DS.'Plug'.DS.$param['name'].'.html';
                   
                     
                    if (file_exists($path)){
                        $templateObj = new Template($path, $data,$this->id,false);
                        $content.=$templateObj->getHtml();
                    }
                    
                }

            }

            $html =  str_replace($item['input'],$content,$html);

        }

        return $html;
    }

    protected function compileWebPack(string $html,array $data):string{

        $_all = Tag::cms($html,'webpack');
    
        foreach($_all  as $item){

            $webpackObj = new Webpack($item['content'],$item['param'],$this->path,$this->webpackSaveFolder,$this->webpackUrl);
            $webpackObj->setPlugApp($this->plugApp);
            $html =  str_replace($item['input'],$webpackObj->pack(),$html);

        }

        return $html;
    }

    protected function compileImport(string $html,array $data):string{
         
        $webpackObj = new Webpack($html,$data,$this->path,$this->webpackSaveFolder,$this->webpackUrl);
        $webpackObj->setName($this->id);
        $webpackObj->setPlugApp($this->plugApp);
        $re  =  $webpackObj->htmlImportPack();
         
        $js = $re['js'];
        $css = $re['css'];
        $html = $re['html'];
        
        if ($this->root){
            if ($js !=''){
                if (strpos($html,'<script')!==false){
                    $html = substr_replace($html,$js,strpos($html,'<script'),0);
                }elseif(strpos($html,'</head>')!==false) {
                    $html = substr_replace($html,$js,strpos($html,'</head>'),0);
                }else{
                    $html = $js.$html;
                }
            }
    
    
            if ($css !=''){
                if (strpos($html,'<style')!==false){
                    $html = substr_replace($html,$css,strpos($html,'<style'),0);
                }elseif (strpos($html,'<script')!==false){
                    $html = substr_replace($html,$css,strpos($html,'<script'),0);
                }elseif(strpos($html,'</head>')!==false) {
                    $html = substr_replace($html,$css,strpos($html,'</head>'),0);
                }else{
                    $html = $css.$html;
                }
            }
        }
        

        return $html;
    }

    protected function compileAppLabel(string $html,array $data):string{

        $plugs = Cubox::plug('Label');
        if (!empty($plugs)){
            //全部应用
            $app =[];
            foreach($plugs as $appName => $plug){
                $list = $plug->getLabelList();
                foreach($list as $name){
                    $_all = Tag::cms($html,$appName.'.'.$name);
                    if (count($_all )>0){
                        

                        foreach($_all as $item){
                            try{
                                $html = str_replace($item['input'],$plug->$name($this,$item['content'],$item['param']),$html);
                            }catch(\Exception $e){
                                $html = str_replace($item['input'],'<!-- '.$e->getMessage().'  -->',$html);
                            }
                        }
                    }
                }
            }
        }
        return $html;
    }
    
    

    protected function compileFor($html,$data,$current_level=0):string{
        
        if ($current_level==0){

            $current_level = 1;

            $re =  $this->_recursion($html,'for');
        
            if ($re['max']==0) return $html;

            $html = $re['html'];

        }


        $_all = Tag::cms($html,'for_'.$current_level);

        foreach ($_all as $for_item){
            $replace_str = '';

            if (substr($for_item['param']['data'],0,1)!='#'){
                $for_item['param']['data'] = explode(',',$for_item['param']['data']);
            }

            $param = $this->realParamValue($for_item['param'],$data);

            if (!isset($param['key'])) $param['key'] = 'index';
            if (!isset($param['item'])) $param['item'] = 'item';
            
            if(isset($param['data'])){
                $_data = $param['data'];
            }else{
                $_data = null;
            }
           

            if (is_array($_data) || is_object($_data) ) {

                if ($for_item['content']){
                    $index = 1;
                    foreach($_data as $k=>$v){
                        $content = $for_item['content'];
                        $data[$param['key']]  = $k;
                        $data['forIndex']  = $index++;
                        $data[$param['item']] = $v;
                        if(strpos($content,'cms:for')!==false){
                            $content = $this->compileFor($content,$data,$current_level+1);
                        }
                         
                        $replace_str .= $this->compile($content,$data);           
                    }


                }else{
                    $replace_str = '';
                }

            }else{
                $replace_str = '';
            }
 
            $html = str_replace($for_item['input'],$replace_str,$html);

        }

     
        return $html;

    }


    /**
     * 编译IF
     *
     * @param string $html
     * @param [type] $data
     * @return string
     */
    protected function compileIf(string $html,$data):string{
        $_all = Tag::cms($html,'if');

        if (count($_all)==0) return $html;

        foreach($_all as $item){

           $content =  $item['content'];
           $param =  $item['param'];
           $replace_str = '';
           if( $content!=''){
                $left_content   = '';
                $right_content  = '';

                if(strpos($content,'<cms:else />')===false){
                    $left_content   = $content;
                    $right_content  = '';
                }else{
 
                    $arr = explode('<cms:else />',$content);
                    $left_content   = $arr[0];
                    $right_content  = $arr[1];
                }

                $condition = false;

                if (!isset($param['exepr']) || $param['exepr'] == ''  ){
                    $condition = false;
                }else{
                    $condition = $this->_condition_value($param['exepr'],$data);
                }

                if($condition){
                    $replace_str =  $this->compile($left_content,$data);
                }else{
                    if ($right_content!=''){
                        $replace_str =  $this->compile($right_content,$data);
                    }
                }

 
           } 

           $html = str_replace($item['input'],$replace_str,$html);
        }


        
        return $html;
    }
    
    protected function compileJson(string $html,array $data):string{
        $_all = Tag::cms($html,'json');

        foreach($_all as $item){
           $param =  $this->realParamValue($item['param'],$data);
           if (isset($param['data'])){
                
                //手动查找值
                //$_data = $data[$param['data']]; 
                $html =  str_replace($item['input'],json_encode($param['data']),$html);
           }else{
                $html =  str_replace($item['input'],'<!-- cms-json 参数错误 未设置data -->',$html);
           }
        }

        return $html;
    }
    protected function compileValue($html,$data,$ext=''):string{
        
        foreach($data  as $k=>$v){

            if ($ext!='') $k = $ext.'.'.$k;

            if (is_array($v) || is_object($v) ){

                $html= $this->compileValue($html,$v,$k);

            }else{
                
                if ($v==null) $v= '';

                $html =  str_replace('#'.$k.'#',$v,$html);

            }

        }
        
        return $html;
    }

    /**
     * 确定参数的值
     *
     * @param array $param
     * @param [type] $data
     * @return void
     */
    protected function realParamValue(array $param,$data){
        
        foreach ($param as $k=>$v){
             
           if (is_string($v)){
               
               if (substr($v,0,1)=='#' &&  substr($v,-1,1)=='#'){
                    $v =  substr($v,1,strlen($v)-2);
                    

                    switch($v){
                        case 'TEMPLATE_INIT_DATA':
                            $param[$k] =  $this->data;
                        break;
                        
                        case 'TEMPLATE_CURRENT_DATA':
                            $param[$k] =  $data;    
                        break;
                        default:
                            $param[$k] = $this->_get_data_value($data,$v);
                        break;
                    }
                    
               }else{
                   //@todo 暂时不支持默认替换变量，所有的参数变量都要使用 #value#
                   $value = $this->_get_data_value($data,$v);

                   if ($value!='') $param[$k] = $value;
                   else  $param[$k] = $v;
                   //$param[$k] = $v;
               }

           }else{
                $param[$k] = $v;
           }
           
        }
        return $param;
    }

    private function _get_data_value($data,$name){
        
        $arr =explode('.',$name);
        
        foreach($arr as $v){

            if (isset($data[$v])){
                $data = $data[$v]; 
            }else{
                $data = '';
            }

        }

        return $data;

    }

    /**
     * 提前对模板代码内的条件内容 < > 进行转义，防止正则的时候出错
     *
     * @param string $html
     * @return string
     */
    private function parseExepr(string $html):string{
        $reg = '/cms:if[\s]*exepr="[\s\S]*?"/i';
        $line =  preg_replace_callback($reg,function($matches){
            $str = $matches[0];

            $str = str_replace('<','&lt;',$str);
            $str = str_replace('>','&gt;',$str);

            return $str;
        },$html);

        return $line;
    }

    private function _recursion(string $str,string $tag){
        $max_level  = 0;
        $level = 0;
        
        $match_tag =  'cms:'.$tag;

        $re_str = '';

        //没有当前标签，退出递归
        if (strpos($str,$match_tag)===false){
            return array('html'=>$str,'max'=>0);
        }else{
            $over_str = $str;
            while(strpos($over_str,$match_tag)!==false){

                $i = strpos($over_str,$match_tag);

                if (substr($over_str,$i-1,1)=='<'){
                    $level++;
                    if ($max_level<$level) $max_level = $level;
                    $re_str .= substr($over_str,0,$i).$match_tag.'_'.$level;
                }else{
                    $re_str .= substr($over_str,0,$i).$match_tag.'_'.$level;
                    $level--;
                }

                $over_str= substr($over_str,$i+strlen($match_tag));

            }

            $re_str .=$over_str;

        }

        return array('html'=>$re_str,'max'=>$max_level);
    }
   
    /**
     * 执行条件
     *
     * @param string $condition_str
     * @param [type] $data
     * @return void
     */
    private function _condition_value(string $condition_str,$data){
        $re = false;
        $reg = '/(\'[\w\W]*\')|("[\w\W]*")|(#[\w\W]*#)|([A-Za-z_][\w\.]*)/i';
         
        $condition_str = str_replace('&lt;','<',$condition_str);
        $condition_str = str_replace('&gt;','>',$condition_str);
         
        $line =  preg_replace_callback($reg,function($matches){
            $str = $matches[0];

            if (substr($str,0,1)=="'" || substr($str,0,1)=='"') return $str;

            if (substr($str,0,1)=="#"){
                $name  = substr($str,1,strlen($str)-2);
            }else{
                $name  = $str;
            }


            if (strpos($name,'.')===false){
                return '$data["'.$name.'"]';
            }else{

                $arr = explode('.',$name);

                $re  = '$data';

                for($i=0;$i<count($arr);$i++){
                    $re.='["'.$arr[$i].'"]';
                }

                return $re;
            }

        },$condition_str);
        
        try{
            @eval('if('.$line.') $re= true;else $re=false;');
        }catch(\Exception $e){
            $re = false;
        }            
        
        
        return $re;
    }
  

    
    public function clearValue($html){
        //正则替换掉所有##内容
        $html = preg_replace('/#(.+?)#/','',$html);
        return $html;
    }
}